SwiftUI 如何根据获取的数据更改视图的背景颜色?

Posted

技术标签:

【中文标题】SwiftUI 如何根据获取的数据更改视图的背景颜色?【英文标题】:SwiftUI How to change background color of the view based on fetched data? 【发布时间】:2020-08-14 07:34:51 【问题描述】:

我正在 SwiftUI 上开发一个基本的天气应用程序,它会根据天气状态动态改变颜色。一切正常,但我无法将从 JSON 解析的 OpenWeatherMap 图标数据发送到我的视图。看起来它没有将从另一个 Swift 文件发送的信息分配给我声明的变量。我对普通的 Swift 有一些经验,但 SwiftUI 的概念肯定不熟悉。

下面是我的 ContentView.swift。

   import SwiftUI

struct ContentView: View 
    @State private var selected = 0
    @ObservedObject var weather = CurrentWeatherViewModel()
    @State var city : String = ""
    private var height : CGFloat = UIScreen.main.bounds.height
        var body: some View 
            VStack
                HStack
                    TextField("Enter your city", text: $city)
                    self.weather.fetchmetric(self.city)
                    .padding(.horizontal)
                    
                
                    GeometryReader gr in
                        CurrentWeather(weather: self.weather.current, height: self.selected == 0 ? gr.size.height : (gr.size.height*0.75)).frame(width: 375.0, height: 770).modifier(currentViewModifier()).animation(.easeInOut(duration: 0.5))
                    .edgesIgnoringSafeArea(.all)
                    
            .frame(width: 375, height: 735, alignment: .center)
            
        
            
        
    

这是我的 CurrentWeather.swift 文件。 @State 颜色变量是我需要更改的变量。

    import SwiftUI
import UIKit

struct CurrentWeather: View 
    // Added color themes for potential weather scenarios
    let bgColors = [
        "Clear":LinearGradient(gradient: Gradient(colors: [Color( #colorLiteral(red: 0.6544341662, green: 0.9271220419, blue: 0.9764705896, alpha: 1)), Color( #colorLiteral(red: 0.2392156869, green: 0.6745098233, blue: 0.9686274529, alpha: 1))]), startPoint: .top, endPoint: .bottom),
        "Sunny":LinearGradient(gradient: Gradient(colors: [Color( #colorLiteral(red: 0.9764705896, green: 0.850980401, blue: 0.5490196347, alpha: 1)), Color( #colorLiteral(red: 0.9529411793, green: 0.8685067713, blue: 0.1800223484, alpha: 1))]), startPoint: .top, endPoint: .bottom),
        "Partly cloudy":LinearGradient(gradient: Gradient(colors: [Color( #colorLiteral(red: 0.5644291786, green: 0.6156922265, blue: 0.8125274491, alpha: 1)), Color( #colorLiteral(red: 0.3611070699, green: 0.3893437324, blue: 0.5149981027, alpha: 1))]), startPoint: .top, endPoint: .bottom),
        "Cloudy":LinearGradient(gradient: Gradient(colors: [Color( #colorLiteral(red: 0.5088317674, green: 0.5486197199, blue: 0.7256778298, alpha: 1)), Color( #colorLiteral(red: 0.3843137255, green: 0.4117647059, blue: 0.5450980392, alpha: 1))]), startPoint: .top, endPoint: .bottom),
        "Broken clouds":LinearGradient(gradient: Gradient(colors: [Color( #colorLiteral(red: 0.4714559888, green: 0.41813849, blue: 0.4877657043, alpha: 1)), Color( #colorLiteral(red: 0.3823538819, green: 0.3384427864, blue: 0.3941545051, alpha: 1))]), startPoint: .top, endPoint: .bottom),
        "Mist":LinearGradient(gradient: Gradient(colors: [Color( #colorLiteral(red: 0.8536048541, green: 0.8154317929, blue: 0.6934956985, alpha: 1)), Color( #colorLiteral(red: 0.5, green: 0.3992742327, blue: 0.3267588525, alpha: 1))]), startPoint: .top, endPoint: .bottom),
        "Patchy rain possible":LinearGradient(gradient: Gradient(colors: [Color( #colorLiteral(red: 0.422871705, green: 0.486337462, blue: 0.7241632297, alpha: 1)), Color(#colorLiteral(red: 0.3826735404, green: 0.4012053775, blue: 0.9529411793, alpha: 1))]), startPoint: .top, endPoint: .bottom),
        "Patchy snow possible":LinearGradient(gradient: Gradient(colors: [Color( #colorLiteral(red: 0.8229460361, green: 0.8420813229, blue: 0.9764705896, alpha: 1)), Color( #colorLiteral(red: 0.6424972056, green: 0.9015246284, blue: 0.9529411793, alpha: 1))]), startPoint: .top, endPoint: .bottom),
        "Patchy sleet possible":LinearGradient(gradient: Gradient(colors: [Color( #colorLiteral(red: 0.9764705896, green: 0.7979655136, blue: 0.9493740175, alpha: 1)), Color( #colorLiteral(red: 0.6843526756, green: 0.7806652456, blue: 0.9529411793, alpha: 1))]), startPoint: .top, endPoint: .bottom),
        "Patchy freezing drizzle possible":LinearGradient(gradient: Gradient(colors: [Color( #colorLiteral(red: 0.6207757569, green: 0.9686274529, blue: 0.9110963382, alpha: 1)), Color( #colorLiteral(red: 0.4745098054, green: 0.8392156959, blue: 0.9764705896, alpha: 1))]), startPoint: .top, endPoint: .bottom),
        "Thundery outbreaks possible":LinearGradient(gradient: Gradient(colors: [Color( #colorLiteral(red: 0.3647058904, green: 0.06666667014, blue: 0.9686274529, alpha: 1)), Color( #colorLiteral(red: 0.1764705926, green: 0.01176470611, blue: 0.5607843399, alpha: 1))]), startPoint: .top, endPoint: .bottom),
        "Blowing snow":LinearGradient(gradient: Gradient(colors: [Color( #colorLiteral(red: 0.1764705926, green: 0.01176470611, blue: 0.5607843399, alpha: 1)), Color( #colorLiteral(red: 0.09019608051, green: 0, blue: 0.3019607961, alpha: 1))]), startPoint: .top, endPoint: .bottom),
        "Thunderstorm":LinearGradient(gradient: Gradient(colors: [Color( #colorLiteral(red: 0.9551106616, green: 0.9764705896, blue: 0.9351792135, alpha: 1)), Color( #colorLiteral(red: 0.6891936611, green: 0.7095901305, blue: 0.9529411793, alpha: 1))]), startPoint: .top, endPoint: .bottom),
        "Fog":LinearGradient(gradient: Gradient(colors: [Color( #colorLiteral(red: 0.6324083141, green: 0.8039215803, blue: 0.7850640474, alpha: 1)), Color( #colorLiteral(red: 0.4545597353, green: 0.393878495, blue: 0.5369011739, alpha: 1))]), startPoint: .top, endPoint: .bottom),
        "Freezing fog":LinearGradient(gradient: Gradient(colors: [Color( #colorLiteral(red: 0.8039215803, green: 0.8039215803, blue: 0.8039215803, alpha: 1)), Color( #colorLiteral(red: 0.4545597353, green: 0.393878495, blue: 0.5369011739, alpha: 1))]), startPoint: .top, endPoint: .bottom),
        "Patchy light drizzle":LinearGradient(gradient: Gradient(colors: [Color( #colorLiteral(red: 0.5892893535, green: 0.7170531098, blue: 0.9764705896, alpha: 1)), Color( #colorLiteral(red: 0.2392156869, green: 0.6745098233, blue: 0.9686274529, alpha: 1))]), startPoint: .top, endPoint: .bottom),
        "Light rain":LinearGradient(gradient: Gradient(colors: [Color( #colorLiteral(red: 0.2392156869, green: 0.6745098233, blue: 0.9686274529, alpha: 1)), Color( #colorLiteral(red: 0.2854045624, green: 0.4267300284, blue: 0.6992385787, alpha: 1))]), startPoint: .top, endPoint: .bottom),
        "Moderate rain":LinearGradient(gradient: Gradient(colors: [Color( #colorLiteral(red: 0.3437546921, green: 0.6157113381, blue: 0.7179171954, alpha: 1)), Color( #colorLiteral(red: 0.4118283819, green: 0.5814552154, blue: 0.6975531409, alpha: 1))]), startPoint: .top, endPoint: .bottom),
        "Heavy rain":LinearGradient(gradient: Gradient(colors: [Color( #colorLiteral(red: 0.1764705926, green: 0.4980392158, blue: 0.7568627596, alpha: 1)), Color( #colorLiteral(red: 0.1596036421, green: 0, blue: 0.5802268401, alpha: 1))]), startPoint: .top, endPoint: .bottom),
        "Light freezing rain":LinearGradient(gradient: Gradient(colors: [Color( #colorLiteral(red: 0.7433765433, green: 0.9529411793, blue: 0.8886958889, alpha: 1)), Color( #colorLiteral(red: 0.4561494407, green: 0.6342332627, blue: 0.7568627596, alpha: 1))]), startPoint: .top, endPoint: .bottom),
        "Heavy rain at times":LinearGradient(gradient: Gradient(colors: [Color( #colorLiteral(red: 0.1764705926, green: 0.4980392158, blue: 0.7568627596, alpha: 1)), Color( #colorLiteral(red: 0.1596036421, green: 0, blue: 0.5802268401, alpha: 1))]), startPoint: .top, endPoint: .bottom),
        "defaultStatus":LinearGradient(gradient: Gradient(colors: [Color(#colorLiteral(red: 0.9372549057, green: 0.3490196168, blue: 0.1921568662, alpha: 1)),Color(#colorLiteral(red: 0.9686274529, green: 0.78039217, blue: 0.3450980484, alpha: 1))]), startPoint: .top, endPoint: .bottom)
    ]
    
    var weather : Weather?
    var height : CGFloat = 0
    
   @State var color : String = "defaultStatus"
    
    var body: some View 
        NavigationView
        VStack(alignment: .center, spacing: 10) 
            Image(weather?.weather.last?.icon ?? "01d")
                .resizable()
                .frame(width: 130, height: 130)
                .aspectRatio(contentMode: .fit)
            Text("Today in \(weather?.name ?? "Unknown")")
                .font(.title)
                .foregroundColor(.white)
            .bold()
            .padding()
            HStack
                Text("\(weather?.main.temp ?? 0)°")
                    .foregroundColor(.white)
                    .fontWeight(.heavy)
                    .font(.system(size: 50))
               
            
            Text("\(weather?.weather.last?.description ?? "Unknown")")
                .foregroundColor(.white)
                .font(.body)
            
        .frame(width: height, height: height)
            .background(bgColors[color])
            
        .navigationBarItems(trailing:
                                NavigationLink(destination: Settings()) 
                                    Image(systemName: "gear").imageScale(.large).accentColor(.black)
                            )
        .onAppear  // Code Execution at startup
    


struct currentViewModifier : ViewModifier
    private var radius : CGFloat = 20
    private var xAxis : CGFloat = 20
    private var yAxis : CGFloat = 20
    
    func body(content: Content) -> some View 
        content
            .cornerRadius(radius)
            
            .opacity(1.0)
        
    

这是我的 WeatherModel.swift 文件,其中我有执行获取的函数和让代码运行的 background() 函数。

    import Foundation
import Combine

final class CurrentWeatherViewModel : ObservableObject
    @Published var current : Weather?
    
    init() 
        DispatchQueue.main.async 
            //if Settings().selected == 0
                self.fetchmetric()
            //
           // else
                self.fetchimperial()
           // 
        
    


// fetch functions for metric and imperial and set color at the home screen

extension CurrentWeatherViewModel 
    func fetchmetric(_ city : String = "london")
        let icon = current?.weather.last?.icon
        API.fetchCurrentmetricWeather(by: city) 
            // Work In Progress
            
            self.current = $0
            CurrentWeather().color = self.backgroundColor(code: icon ?? "aaa")
        
    
    
    func fetchimperial(_ city : String = "london")
        API.fetchCurrentimperialWeather(by: city) 
            self.current = $0
            
        
    
    func backgroundColor(code : String) -> String 
        
        switch code 
            case "01d":
                return "Clear"
            case "02d":
                return "Partly cloudy"
            case "02n":
                return "Partly cloudy"
            case "03d":
                return "Cloudy"
            case "03n":
                return "Cloudy"
            case "04d":
                return "Broken clouds"
            case "04n":
                return "Broken clouds"
            case "09d":
                return "Moderate Rain"
            case "09n":
                return "Heavy rain"
            case "10d":
                return "Moderate Rain"
            case "10n":
                return "Heavy rain"
            case "11d":
                return "Thunderstorm"
            case "11n":
                return "Thunderstorm"
            case "13d":
                return "Snow"
            case "13n":
                return "Snow"
            case "50d":
                return "Mist"
            case "50n":
                return "Mist"
            default:
                return "defaultStatus"
            
        
        

我的问题是,它没有将图标数据发送到 CurrentWeather 颜色变量,我错过了什么?我必须说我不熟悉@State 变量并尝试学习 SwiftUI。

感谢代码上的任何输入。

【问题讨论】:

【参考方案1】:

表达式 CurrentWeather().color = self.backgroundColor(code: icon ?? "aaa") 没有意义,因为修改了本地创建的值(CurrentWeather() 创建了结构值)。

相反,您需要在视图模型中为颜色创建已发布的属性并使用它,例如

final class CurrentWeatherViewModel : ObservableObject
    @Published var current : Weather?
    @Publisehd var color: String = "aaa"

然后更新为

API.fetchCurrentmetricWeather(by: city)  weather in
    // Work In Progress
    
    DispatchQueue.main.async  // update published on main queue !!!
       self.current = weather
       self.color = self.backgroundColor(code: icon ?? "aaa")
    

然后在视图中使用它

    .frame(width: height, height: height)
        .background(bgColors[self.weather?.color ?? "defaultStatus"])

不需要本地视图@State var color

【讨论】:

感谢您的意见。当我按照您的指示放置代码时,我在 DispatchQueue 行上收到“上下文闭包类型'@convention(block) () -> Void' 需要 0 个参数,但在闭包主体中使用了 1”。 self.weather?.color 似乎不存在,我使用了bgColors[self.model?.color ?? "defaultStatus"],因为我认为您的意思是 CurrentWeatherViewModel。代码运行良好,但仍获得默认输入。

以上是关于SwiftUI 如何根据获取的数据更改视图的背景颜色?的主要内容,如果未能解决你的问题,请参考以下文章

如何根据 SwiftUI 中的 @State 更改导航视图的导航视图样式?

SwiftUI:如何根据滚动视图的用户当前滚动位置同步/更改进度条进度?

更改选项卡式视图栏颜色 SwiftUI

SwiftUI - 如何模糊视图的默认背景颜色?

SwiftUI:如何更改 NavigationView 的色调(背景颜色)?

SwiftUI - 如何在编辑模式下更改列表的背景