SwiftUI Widget 背景基于值传递的图像 url 或渐变背景 [英] SwiftUI Widget background based on the value passed image url or gradient background

查看:42
本文介绍了SwiftUI Widget 背景基于值传递的图像 url 或渐变背景的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我想要做的是让用户可以选择widget background 是从http 中获取的图像还是渐变背景代码>.

我目前有以下笔记结构,但我无法让它发挥作用.

所以typeBg必须有一个默认值,如果不通过就取默认值.

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

struct 注意:Identifiable, Codable {让标题:字符串让消息:字符串让图像:字符串?让 bgColors: [颜色?]//[字符串?]让 typeBg: 字符串?=颜色"var id = UUID()}

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

<块引用>

类型注意"不符合协议可解码"

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

我想做的是:

如果 typeBg of the Struct == 'url',那么我取值 image 这是一个 url.

如果 typeBg of the Struct == 'gradient',那么我取值 bgColors 这是一个 Color 数组.>

内容视图:

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

小部件:

struct SmallWidget:查看{var 条目:注意@Environment(\.colorScheme) var colorSchemefunc bg() ->AnyView {//<- 没有工作切换 entry.typeBg {案例网址":返回 AnyView(NetworkImage(url: URL(string: entry.image))案例梯度":返回 AnyView(线性梯度(渐变:渐变(颜色:entry.bgColors),起点:.top,端点:.bottom))默认:返回 AnyView(Color.blue)}var主体:一些视图{GeometryReader { geo inVStack(对齐:.center){文本(条目.标题).font(.title).胆大().minimumScaleFactor(0.5).foregroundColor(.white).阴影(颜色: Color.black,半径:1.0,x: CGFloat(4),y: CGFloat(4))文本(条目.消息).foregroundColor(Color.gray).阴影(颜色: Color.black,半径:1.0,x: CGFloat(4),y: CGFloat(4))}.frame(maxWidth: .infinity, maxHeight: .infinity).edgesIgnoringSafeArea(.all)}.背景(背景)//.背景(渐变)//.background(NetworkImage(url: URL(string: entry.image)))}}

struct NetworkImage:查看{公开让网址:网址?var主体:一些视图{团体 {如果让 url = url,让 imageData = 试试?数据(内容:网址),让 uiImage = UIImage(data: imageData) {图像(uiImage:uiImage).resizable().aspectRatio(contentMode: .fill)}别的 {进度视图()}}}}

解决方案

这花了很长时间做,因为 Color 不是 Codable,所以自定义版本有被制造.这是我得到的:

struct 注意:Identifiable, Codable {枚举 CodingKeys:CodingKey {案例标题、信息、背景}让 id = UUID()让标题:字符串让消息:字符串让背景:NoteBackground}扩展注{枚举 NoteBackground: Codable {枚举 NoteBackgroundError: 错误 {case failedToDecode}案例网址(字符串)案例渐变([颜色])init(来自解码器:解码器)抛出{让容器 = 尝试decoder.singleValueContainer()如果让 url = 尝试?容器.解码(字符串.self){self = .url(url)返回}如果让渐变=尝试?container.decode([ColorWrapper].self) {self = .gradient(gradient.map(\.color))返回}抛出 NoteBackgroundError.failedToDecode}func encode(to encoder: Encoder) 抛出 {var 容器 = 编码器.singleValueContainer()切换自我{case let .url(url):尝试 container.encode(url)case let .gradient(gradient):让颜色 = gradient.map(ColorWrapper.init(color:))尝试 container.encode(colors)}}}}

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

enum ColorConvert {结构组件:Codable {让红色:双让绿色:双让蓝色:双让不透明度:双}static func toColor(来自组件:组件)->颜色 {颜色(红色:components.red,绿色:components.green,蓝色:components.blue,不透明度:components.opacity)}static func toComponents(from color: Color) ->成分?{守卫让组件 = color.cgColor?.components else { return nil }守卫 components.count == 4 else { return nil }让转换 = components.map(Double.init)返回组件(红色:转换[0],绿色:转换[1],蓝色:转换[2],不透明度:转换[3])}}struct ColorWrapper: Codable {让颜色:颜色初始化(颜色:颜色){self.color = 颜色}init(来自解码器:解码器)抛出{让容器 = 尝试decoder.singleValueContainer()让组件 = 尝试 container.decode(ColorConvert.Components.self)color = ColorConvert.toColor(来自:组件)}func encode(to encoder: Encoder) 抛出 {var 容器 = 编码器.singleValueContainer()let components = ColorConvert.toComponents(from: color)尝试 container.encode(components)}}

它可以像这样使用:

struct ContentView: 查看 {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,蓝色:0.8)]))var主体:一些视图{文本(字符串(描述:数据)).onAppear(执行:测试)}私人功能测试(){做 {让编码数据 = 尝试 JSONEncoder().encode(data)打印(编码",encodedData.base64EncodedString())让 decodedData = 尝试 JSONDecoder().decode(Note.self, from: encodingData)打印(解码",字符串(描述:decodedData))} 抓住让错误{致命错误(错误:\(error.localizedDescription)")}}}

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

对你来说,你可以做这样的事情来根据entrybackground改变背景:

@ViewBuilder func bg() ->一些视图{切换 entry.background {case let .url(url):网络图像(网址:网址(字符串:网址))case let .gradient(colors):线性梯度(渐变:渐变(颜色:颜色),起点:.top,终点:.bottom)///可以在此处为纯色的 `NoteBackground` 枚举添加另一个案例}}

What I would like to do is give the user the option to choose whether the widget background is an image taken from http or a gradient background.

I currently have the following note structure, but I can't get it to work.

So typeBg must have a default value, if not passed it should take the default value.

The values of image and bgColors must be optional parameters.

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

But I only get errors, in the struct Note:

Type 'Note' does not conform to protocol 'Decodable'

Type 'Note' does not conform to protocol 'Encodable'

What I would like to do is:

if typeBg of the Struct == 'url', then I take as value image which is a url.

if typeBg of the Struct == 'gradient', then I take as value bgColors which is an array of Color.

ContentView:

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

SmallWidget:

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()
            }
        }
        
    }
}

解决方案

This took quite a while to do, because Color is not Codable, so a custom version had to be made. Here is what I got:

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)
            }
        }
    }
}

To make Color be Codable, it is wrapped in 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)
    }
}

It can then be used like so:

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)")
        }
    }
}

Note: the Color you encode cannot be something like Color.red - it has to be made from the RGB components like using the Color(red:green:blue:) initializer.

For you, you could do something like this to change the background depending on entry's background:

@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
    }
}

这篇关于SwiftUI Widget 背景基于值传递的图像 url 或渐变背景的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆