【问题标题】:iOS SwiftUI Searchbar and REST-APIiOS SwiftUI 搜索栏和 REST-API
【发布时间】:2019-09-19 13:47:47
【问题描述】:

我正在试验 SwiftUI,并希望使用搜索字符串从我的 REST API 中获取更新。

但是,我现在不确定如何将这两个组件组合在一起。

我希望你有一个想法。

这是我的代码:

struct ContentView: View {
    @State private var searchTerm: String = ""
    @ObservedObject var gameData: GameListViewModel = GameListViewModel(searchString: ### SEARCH STRING ???? ###)

    var body: some View {
        NavigationView{
            Group{
                // Games werden geladen...
                if(self.gameData.isLoading) {
                    LoadingView()
                }
                // Games sind geladen:
                else{
                    VStack{
                        // Suche:
                        searchBarView(text: self.$searchTerm)

                        // Ergebnisse:
                        List(self.gameData.games){ game in
                            NavigationLink(destination: GameDetailView(gameName: game.name ?? "0", gameId: 0)){
                                HStack {
                                    VStack(alignment: .leading, spacing: 2) {
                                        Text(game.name ?? "Kein Name gefunden")
                                            .font(.headline)
                                        Text("Cover: \(game.cover?.toString() ?? "0")")
                                            .font(.subheadline)
                                            .foregroundColor(.gray)
                                    }
                                }
                            }
                        }
                    }
                }
           }
       .navigationBarTitle(Text("Games"))
       }
   }
}

以及搜索栏的实现:

import Foundation
import SwiftUI

struct searchBarView: UIViewRepresentable {

    @Binding var text:String

    class Coordinator: NSObject, UISearchBarDelegate {

        @Binding var text: String

        init(text: Binding<String>){
            _text = text
        }

        func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
            print(searchText)
            text = searchText
        }
    }
    func makeCoordinator() -> searchBarView.Coordinator {
        return Coordinator(text: $text)
    }
    func makeUIView(context: UIViewRepresentableContext<searchBarView>) -> UISearchBar {
        let searchBar = UISearchBar(frame: .zero)
        searchBar.delegate = context.coordinator
        return searchBar
    }
    func updateUIView(_ uiView: UISearchBar, context: UIViewRepresentableContext<searchBarView>) {
        uiView.text = text
    }
}

【问题讨论】:

    标签: ios swift rest swiftui swift5


    【解决方案1】:

    搜索文本应该在视图模型内。

    final class GameListViewModel: ObservableObject {
    
        @Published var isLoading: Bool = false
        @Published var games: [Game] = []
    
        var searchTerm: String = ""
    
        private let searchTappedSubject = PassthroughSubject<Void, Error>()
        private var disposeBag = Set<AnyCancellable>()
    
        init() {
            searchTappedSubject
            .flatMap {
                self.requestGames(searchTerm: self.searchTerm)
                    .handleEvents(receiveSubscription: { _ in
                        DispatchQueue.main.async {
                            self.isLoading = true
                        }
                    },
                    receiveCompletion: { comp in
                        DispatchQueue.main.async {
                            self.isLoading = false
                        }
                    })
                    .eraseToAnyPublisher()
            }
            .replaceError(with: [])
            .receive(on: DispatchQueue.main)
            .assign(to: \.games, on: self)
            .store(in: &disposeBag)
        }
    
        func onSearchTapped() {
            searchTappedSubject.send(())
        }
    
        private func requestGames(searchTerm: String) -> AnyPublisher<[Game], Error> {
            guard let url = URL(string: "https://jsonplaceholder.typicode.com/posts") else {
                return Fail(error: URLError(.badURL))
                    .mapError { $0 as Error }
                    .eraseToAnyPublisher()
            }
            return URLSession.shared.dataTaskPublisher(for: url)
                   .map { $0.data }
                   .mapError { $0 as Error }
                   .decode(type: [Game].self, decoder: JSONDecoder())
                .map { searchTerm.isEmpty ? $0 : $0.filter { $0.title.contains(searchTerm) } }
                   .eraseToAnyPublisher()
        }
    
    }
    

    每次调用onSearchTapped,都会触发新游戏的请求。

    这里发生了很多事情 - 让我们从 requestGames 开始。

    我正在使用 JSONPlaceholder 免费 API 来获取一些数据并将其显示在列表中。

    requestGames 执行网络请求,从收到的Data 解码[Game]。除此之外,返回的数组会使用搜索字符串进行过滤(由于免费 API 的限制 - 在现实世界的场景中,您会在请求 URL 中使用查询参数)。

    现在让我们看看视图模型构造函数。

    事件顺序为:

    • 获取“搜索点击”主题。
    • 执行网络请求 (flatMap)
    • flatMap 内部,处理加载逻辑(在主队列上调度,因为isLoading 使用下面的Publisher,如果在后台线程上发布值会出现警告)。
    • replaceError 将发布者的错误类型更改为 Never,这是 assign 运算符的要求。
    • receiveOn 是必要的,因为由于网络请求,我们可能仍在后台队列中 - 我们希望在主队列上发布结果。
    • assign 更新视图模型上的数组 games
    • storeCancellable 保存在disposeBag

    这是视图代码(为了演示,没有加载):

    struct ContentView: View {
    
        @ObservedObject var viewModel = GameListViewModel()
    
        var body: some View {
            NavigationView {
                Group {
                   VStack {
                        SearchBar(text: $viewModel.searchTerm,
                                  onSearchButtonClicked: viewModel.onSearchTapped)
                        List(viewModel.games, id: \.title) { game in
                            Text(verbatim: game.title)
                        }
                    }
                }
                .navigationBarTitle(Text("Games"))
            }
        }
    
    }
    

    搜索栏实现

    struct SearchBar: UIViewRepresentable {
    
        @Binding var text: String
        var onSearchButtonClicked: (() -> Void)? = nil
    
        class Coordinator: NSObject, UISearchBarDelegate {
    
            let control: SearchBar
    
            init(_ control: SearchBar) {
                self.control = control
            }
    
            func searchBar(_ searchBar: UISearchBar, textDidChange searchText: String) {
                control.text = searchText
            }
    
            func searchBarSearchButtonClicked(_ searchBar: UISearchBar) {
                control.onSearchButtonClicked?()
            }
        }
    
        func makeCoordinator() -> Coordinator {
            return Coordinator(self)
        }
    
        func makeUIView(context: UIViewRepresentableContext<SearchBar>) -> UISearchBar {
            let searchBar = UISearchBar(frame: .zero)
            searchBar.delegate = context.coordinator
            return searchBar
        }
        func updateUIView(_ uiView: UISearchBar, context: UIViewRepresentableContext<SearchBar>) {
            uiView.text = text
        }
    
    }
    

    【讨论】:

    • 谢谢。这可行,我对其进行了调整,以便将参数附加到请求中。但是现在视图在键入时会永久重新加载,因此我必须在搜索字段中一次又一次地单击。当我点击“发送”按钮时,我如何重写它以重新加载它(仅)?
    • @Flolle 更新了我的答案以及搜索栏的实现(在答案的底部找到它)。
    • 你太棒了——非常感谢你!但是我还有很多东西要学,必须仔细研究你的代码。新的 Swift 机制对我来说很不寻常。
    • 很高兴能帮上忙。开始的最佳方式是观看 WWDC 视频 - 尤其是有关 SwiftUI 和 Combine 框架的视频。
    【解决方案2】:

    不需要涉及 UIKit,你可以像这样声明一个简单的搜索栏:

    struct SearchBar: View {
    
        @State var searchString: String = ""
    
        var body: some View {
    
            HStack {
                TextField("Start typing",
                          text: $searchString,
                          onCommit: { self.performSearch() })
                    .textFieldStyle(RoundedBorderTextFieldStyle())
                Button(action: { self.performSearch() }) {
                    Image(systemName: "magnifyingglass")
                }
            }   .padding()
        }
    
        func performSearch() {
    
        }
    }
    

    然后将搜索逻辑放在 performSearch() 中。

    【讨论】:

    • 被低估的评论,+1
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-12-07
    • 2021-07-21
    • 1970-01-01
    • 1970-01-01
    • 2020-04-28
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多