SwiftUI Widget 背景基于值传递的图像 url 或渐变背景 [英] SwiftUI Widget background based on the value passed image url or gradient background
问题描述
我想要做的是让用户可以选择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:)
初始值设定项.
对你来说,你可以做这样的事情来根据entry
的background
改变背景:
@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屋!