如何在自定義 SwiftUI 視圖中設置文本字段和按鈕

JS_is_awesome18

我正在嘗試將我的 openweathermap 應用程序中的文本字段和按鈕配置為在主內容視圖之外的自己的視圖中。在 TextFieldView 中,按鈕的動作設置為調用 API 響應。然後,來自響應的天氣數據填充到基於工作表的 DetailView,它由 TextFieldView 中的按鈕觸發。我在工作表中配置了 ForEach 方法以返回添加到 WeatherModel 數組的最後一個城市(從技術上講,這將是最近輸入到文本字段中的城市),然後使用該城市的天氣數據填充基於工作表的 DetailView。以前,當我在 ContentView 中設置包含文本字段、按鈕和工作表控件的 HStack 時,工作表將正確顯示剛剛輸入文本字段的城市的天氣。將這些項目移動到單獨的 TextFieldView 後,ForEach 方法似乎已停止工作。相反,在文本字段中輸入城市名稱後返回的天氣信息顯示在錯誤的計數上。例如,如果我在文本字段中輸入“London”,則工作表中的 DetailView 是完全空白的。如果我隨後輸入“羅馬”作為下一個條目,工作表中的 DetailView 將顯示前一個“倫敦”條目的天氣信息。在文本字段中輸入“巴黎”會顯示“羅馬”的天氣信息,依此類推... 工作表中的 DetailView 是完全空白的。如果我隨後輸入“羅馬”作為下一個條目,工作表中的 DetailView 將顯示前一個“倫敦”條目的天氣信息。在文本字段中輸入“巴黎”會顯示“羅馬”的天氣信息,依此類推... 工作表中的 DetailView 是完全空白的。如果我隨後輸入“羅馬”作為下一個條目,工作表中的 DetailView 將顯示前一個“倫敦”條目的天氣信息。在文本字段中輸入“巴黎”會顯示“羅馬”的天氣信息,依此類推...

總而言之,在我將整個文本字段和按鈕功能移動到單獨的視圖後,工作表中的 ForEach 方法停止正常工作。知道為什麼我描述的問題會發生嗎?

這是我的代碼:

內容視圖

struct ContentView: View {
    // Whenever something in the viewmodel changes, the content view will know to update the UI related elements
    @StateObject var viewModel = WeatherViewModel()
        
    var body: some View {
        NavigationView {
            VStack(alignment: .leading) {
                List {
                    ForEach(viewModel.cityNameList.reversed()) { city in
                        NavigationLink(destination: DetailView(detail: city), label: {
                            Text(city.name).font(.system(size: 18))
                            Spacer()
                            Text("\(city.main.temp, specifier: "%.0f")°")
                                .font(.system(size: 18))
                        })
                    }
                    .onDelete { indexSet in
                        let reversed = Array(viewModel.cityNameList.reversed())
                        let items = Set(indexSet.map { reversed[$0].id })
                        viewModel.cityNameList.removeAll { items.contains($0.id) }
                    }
                }
                .refreshable {
                    viewModel.updatedAll()
                }
                
                TextFieldView(viewModel: viewModel)
                
            }.navigationBarTitle("Weather", displayMode: .inline)
        }
    }
}
struct ContentView_Previews: PreviewProvider {
    static var previews: some View {
        ContentView()
    }
}

文本字段視圖

struct TextFieldView: View {
    
    @State private var cityName = ""
    @State private var showingDetail = false
    @FocusState var isInputActive: Bool
    
    var viewModel: WeatherViewModel
    
    var body: some View {
        HStack {
            TextField("Enter City Name", text: $cityName)
                .focused($isInputActive)
            Spacer()
            .toolbar {
                ToolbarItemGroup(placement: .keyboard) {
                    Button("Done") {
                        isInputActive = false
                    }
                }
            }
            if isInputActive == false {
                Button(action: {
                    viewModel.fetchWeather(for: cityName)
                    cityName = ""
                    self.showingDetail.toggle()
                }) {
                    Image(systemName: "plus")
                        .font(.largeTitle)
                        .frame(width: 75, height: 75)
                        .foregroundColor(Color.white)
                        .background(Color(.systemBlue))
                        .clipShape(Circle())
                }
                .sheet(isPresented: $showingDetail) {
                    ForEach(0..<viewModel.cityNameList.count, id: \.self) { city in
                        if (city == viewModel.cityNameList.count-1) {
                            DetailView(detail: viewModel.cityNameList[city])
                        }
                    }
                }
            }
        }
        .frame(minWidth: 100, idealWidth: 150, maxWidth: 500, minHeight: 30, idealHeight: 40, maxHeight: 50, alignment: .leading)
            .padding(.leading, 16)
            .padding(.trailing, 16)
    }
}

struct TextFieldView_Previews: PreviewProvider {
    static var previews: some View {
        TextFieldView(viewModel: WeatherViewModel())
    }
}

詳細視圖

struct DetailView: View {
    
    @State private var cityName = ""
    @State var selection: Int? = nil
    
    var detail: WeatherModel
        
    var body: some View {
        VStack(spacing: 20) {
            Text(detail.name)
                .font(.system(size: 32))
            Text("\(detail.main.temp, specifier: "%.0f")&deg;")
                .font(.system(size: 44))
            Text(detail.firstWeatherInfo())
                .font(.system(size: 24))
        }
    }
}

struct DetailView_Previews: PreviewProvider {
    static var previews: some View {
        DetailView(detail: WeatherModel.init())
    }
}

視圖模型

class WeatherViewModel: ObservableObject {
    
    @Published var cityNameList = [WeatherModel]()

    func fetchWeather(for cityName: String) {
        guard let url = URL(string: "https://api.openweathermap.org/data/2.5/weather?q=\(cityName.escaped())&units=imperial&appid=<YourAPIKey>") else { return }

        let task = URLSession.shared.dataTask(with: url) { data, _, error in
            guard let data = data, error == nil else { return }
            do {
                let model = try JSONDecoder().decode(WeatherModel.self, from: data)
                DispatchQueue.main.async {
                    self.addToList(model)
                }
            }
            catch {
                print(error)
            }
        }
        task.resume()
    }
        
    func updatedAll() {
        // keep a copy of all the cities names
        let listOfNames = cityNameList.map{$0.name}
        // fetch the up-to-date weather info
        for city in listOfNames {
            fetchWeather(for: city)
        }
    }
    
    func addToList( _ city: WeatherModel) {
        // if already have this city, just update
        if let ndx = cityNameList.firstIndex(where: {$0.name == city.name}) {
            cityNameList[ndx].main = city.main
            cityNameList[ndx].weather = city.weather
        } else {
            // add a new city
            cityNameList.append(city)
        }
    }
}

模型

struct WeatherModel: Identifiable, Codable {
    let id = UUID()
    var name: String = ""
    var main: CurrentWeather = CurrentWeather()
    var weather: [WeatherInfo] = []
    
    func firstWeatherInfo() -> String {
        return weather.count > 0 ? weather[0].description : ""
    }
}

struct CurrentWeather: Codable {
    var temp: Double = 0.0
    var humidity = 0
}

struct WeatherInfo: Codable {
    var description: String = ""
}
工作犬

您需要使用ObservedObjectin yourTextFieldView來使用您@StateObject var viewModel在其中創建的原始(單一事實來源)ContentView並觀察對它的任何更改。

所以使用這個:

struct TextFieldView: View {
    @ObservedObject var viewModel: WeatherViewModel
    ...
}

本文收集自互联网,转载请注明来源。

如有侵权,请联系 [email protected] 删除。

编辑于
0

我来说两句

0 条评论
登录 后参与评论

相关文章

如何在SwiftUI中实现自定义委托

如何在SwiftUI中自定义文本

如何在SwiftUI中添加自定义容器视图

如何在SwiftUI中显示AlertView或自定义覆盖

SwiftUI-如何更改文本字段的文本颜色?

如何在SwiftUI中自定义Slider蓝线?

如何在SwiftUI中实现文本字段列表而不中断删除

如何在SwiftUI中实现自定义日历?

OSX中的SwiftUI自定义文本字段

如何在SwiftUI中设置自定义环境键?

如何在Mac OS的SwiftUI中创建多行文本字段?

如何创建一组文本字段,这些文本字段具有在SwiftUI中编辑的单独变量?

如何在SwiftUI中绘制自定义形状?

如何在swiftui中自定义导航栏

如何使用户在SwiftUI文本字段中仅使用数字输入货币,同时保留$和。?

如何在swiftui(macos)中向工具栏添加文本字段

如何在SwiftUI中自动填充文本字段?

如何在 swiftui 中添加自定义字体?

如何在 SwiftUI 中创建自定义动态形状?

SwiftUI:如何阻止文本字段中的某些字符?

SwiftUI - 如何修改数组中的文本字段

如何通過單擊 SwiftUI 中的按鈕轉到視圖

SwiftUI - 單擊按鈕時添加一行多個文本字段/視圖

SwiftUI 如何為文本字段中的每個字符設置動畫?

如何在函數中傳入 SwiftUI 視圖?

在 SwiftUI 中按下自定義切換元素時如何停止閃爍

為什麼我無法在 SwiftUI 中清除自定義文本字段?

如何創建自定義修飾符以在 SwiftUI 中操作自定義視圖?

如何在 swiftUI 中创建自定义形状