【问题标题】:SwiftUI Widget background based on the value passed image url or gradient backgroundSwiftUI Widget 背景基于传值的图像 url 或渐变背景
【发布时间】:2021-02-07 11:01:47
【问题描述】:

我想做的是让用户选择widget background 是从http 还是gradient background 拍摄的图像。

我目前有以下笔记结构,但我无法让它工作。

所以typeBg必须有一个默认值,如果没有通过它应该取默认值。

image和bgColors的值必须是可选参数。

struct Note: Identifiable, Codable {
    let title: String
    let message: String
    let image: String?
    let bgColors: [Color?]//[String?]
    let typeBg: String? = "color"
    
    var id = UUID()
}

但我只得到错误,在结构注:

类型'Note'不符合协议'Decodable'

类型'Note'不符合协议'Encodable'

我想做的是:

如果结构的typeBg == 'url',那么我将image 作为一个url。

如果结构的typeBg == 'gradient',那么我将bgColors 作为值,这是一个颜色数组。

内容视图:

SmallWidget(entry: Note(title: "Title", message: "Mex", bgColors: bgColors, typeBg: "gradient"))

小部件:

struct SmallWidget: View {
    var entry: Note
    @Environment(\.colorScheme) var colorScheme
    
    
    func bg() -> AnyView { //<- No work
        switch entry.typeBg {
        case "url":
            return AnyView(NetworkImage(url: URL(string: entry.image))
        case "gradient":
            return AnyView(
                LinearGradient(
                    gradient: Gradient(colors: entry.bgColors),
                    startPoint: .top,
                    endPoint: .bottom)
            )
        default:
            return AnyView(Color.blue)
        }
        
        var body: some View {
            GeometryReader { geo in
                VStack(alignment: .center){
                    Text(entry.title)
                        .font(.title)
                        .bold()
                        .minimumScaleFactor(0.5)
                        .foregroundColor(.white)
                        .shadow(
                            color: Color.black,
                            radius: 1.0,
                            x: CGFloat(4),
                            y: CGFloat(4))
                    Text(entry.message)
                        .foregroundColor(Color.gray)
                        .shadow(
                            color: Color.black,
                            radius: 1.0,
                            x: CGFloat(4),
                            y: CGFloat(4))
                    
                }
                .frame(maxWidth: .infinity, maxHeight: .infinity)
                .edgesIgnoringSafeArea(.all)
            }
            .background(bg)
            //.background(gradient)
            //.background(NetworkImage(url: URL(string: entry.image)))
        }
    }
struct NetworkImage: View {
    
    public let url: URL?
    
    var body: some View {
        Group {
            if let url = url, let imageData = try? Data(contentsOf: url),
               let uiImage = UIImage(data: imageData) {
                
                Image(uiImage: uiImage)
                    .resizable()
                    .aspectRatio(contentMode: .fill)
            }
            else {
                ProgressView()
            }
        }
        
    }
}

【问题讨论】:

    标签: swift swiftui widget widgetkit


    【解决方案1】:

    这花了很长时间,因为Color 不是Codable,所以必须制作一个自定义版本。这是我得到的:

    struct Note: Identifiable, Codable {
        
        enum CodingKeys: CodingKey {
            case title, message, background
        }
        
        let id = UUID()
        let title: String
        let message: String
        let background: NoteBackground
    }
    
    
    extension Note {
        
        enum NoteBackground: Codable {
            
            enum NoteBackgroundError: Error {
                case failedToDecode
            }
            
            case url(String)
            case gradient([Color])
            
            init(from decoder: Decoder) throws {
                let container = try decoder.singleValueContainer()
                
                if let url = try? container.decode(String.self) {
                    self = .url(url)
                    return
                }
                if let gradient = try? container.decode([ColorWrapper].self) {
                    self = .gradient(gradient.map(\.color))
                    return
                }
                
                throw NoteBackgroundError.failedToDecode
            }
            
            func encode(to encoder: Encoder) throws {
                var container = encoder.singleValueContainer()
                
                switch self {
                case let .url(url):
                    try container.encode(url)
                case let .gradient(gradient):
                    let colors = gradient.map(ColorWrapper.init(color:))
                    try container.encode(colors)
                }
            }
        }
    }
    

    要使Color 成为Codable,它被包裹在ColorWrapper 中:

    enum ColorConvert {
        
        struct Components: Codable {
            let red: Double
            let green: Double
            let blue: Double
            let opacity: Double
        }
        
        static func toColor(from components: Components) -> Color {
            Color(
                red: components.red,
                green: components.green,
                blue: components.blue,
                opacity: components.opacity
            )
        }
        
        static func toComponents(from color: Color) -> Components? {
            guard let components = color.cgColor?.components else { return nil }
            guard components.count == 4 else { return nil }
            let converted = components.map(Double.init)
            
            return Components(
                red: converted[0],
                green: converted[1],
                blue: converted[2],
                opacity: converted[3]
            )
        }
    }
    
    
    struct ColorWrapper: Codable {
        
        let color: Color
        
        init(color: Color) {
            self.color = color
        }
        
        init(from decoder: Decoder) throws {
            let container = try decoder.singleValueContainer()
            let components = try container.decode(ColorConvert.Components.self)
            color = ColorConvert.toColor(from: components)
        }
        
        func encode(to encoder: Encoder) throws {
            var container = encoder.singleValueContainer()
            let components = ColorConvert.toComponents(from: color)
            try container.encode(components)
        }
    }
    

    然后可以这样使用:

    struct ContentView: View {
        
        let data = Note(title: "Title", message: "Message", background: .url("https://google.com"))
        //let data = Note(title: "Title", message: "Message", background: .gradient([Color(red: 1, green: 0.5, blue: 0.2), Color(red: 0.3, green: 0.7, blue: 0.8)]))
        
        var body: some View {
            Text(String(describing: data))
                .onAppear(perform: test)
        }
        
        private func test() {
            do {
                let encodedData = try JSONEncoder().encode(data)
                print("encoded", encodedData.base64EncodedString())
            
                let decodedData = try JSONDecoder().decode(Note.self, from: encodedData)
                print("decoded", String(describing: decodedData))
            } catch let error {
                fatalError("Error: \(error.localizedDescription)")
            }
        }
    }
    

    注意:您编码的Color不能类似于Color.red - 它必须由RGB 组件组成,例如使用Color(red:green:blue:) 初始化器。

    对你来说,你可以根据entrybackground 来改变背景:

    @ViewBuilder func bg() -> some View {
        switch entry.background {
        case let .url(url):
            NetworkImage(url: URL(string: url))
        case let .gradient(colors):
            LinearGradient(
                gradient: Gradient(colors: colors),
                startPoint: .top,
                endPoint: .bottom
            )
            
        /// CAN ADD ANOTHER CASE TO `NoteBackground` ENUM FOR SOLID COLOR HERE
        }
    }
    

    【讨论】:

    • 感谢您的帮助,我正在对其进行测试,但无法使其正常工作。问题如下: 1) Note 背景,你称之为背景,必须是 SmallWidget 小部件的背景,见上面的代码。我正在尝试做类似GeometryReader {geo in ...}.background (entry.background) 的事情,但它不起作用,它给了我以下错误:Instance method 'background (_: alignment :)' requires that 'Note.NoteBackground 'conform to' View '。那么我该如何使用你设计的结构呢?
    • 2) 如果背景是图像的 url,它会逃避我如何在 NoteBackground 中检索图像,例如从 http 检索图像,我使用 NetworkImage,你可以看到代码上方。
    • @Paul 希望我添加的示例很清楚。您不使用entry.background 作为View,而是使用这些视图的数据。用GeometryReader { geo in ... }.background(bg) 做你以前做过的事情。
    • 谢谢你似乎有效,你对一件事的看法。正如您从图像中看到的那样,我有一个 Picker 来选择要从中获取图像的位置。为避免必须这样做:if select == 0 {SmallWidget (entry: Note(title: arrayBg[select], message: "\(select)", background: .url(url)))} 对于所有其他人也是如此。在您看来,有一种方法可以声明这样的事情:array = [.url(url), .url(url), .gradient(bgColors)] 然后简单地做SmallWidget (entry: Note(title: arrayBg [select], message: "\ (select)", background: array[select]))
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-03
    • 2020-08-19
    • 2012-03-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多