SwiftUI Widget 背景根据传值图片url 或渐变背景
SwiftUI Widget background based on the value passed image url or gradient background
我想做的是让用户选择 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()
}
但我只得到错误,在结构注解中:
Type 'Note' does not conform to protocol 'Decodable'
Type 'Note' does not conform to protocol 'Encodable'
我想做的是:
如果 Struct 的 typeBg
== 'url'
,那么我将 image
作为值 url.
如果 Struct 的 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()
}
}
}
}
这花了很长时间,因为 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:)
初始化程序。
对于你来说,你可以做这样的事情来改变背景,这取决于 entry
的 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
}
}
我想做的是让用户选择 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()
}
但我只得到错误,在结构注解中:
Type 'Note' does not conform to protocol 'Decodable'
Type 'Note' does not conform to protocol 'Encodable'
我想做的是:
如果 Struct 的 typeBg
== 'url'
,那么我将 image
作为值 url.
如果 Struct 的 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()
}
}
}
}
这花了很长时间,因为 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:)
初始化程序。
对于你来说,你可以做这样的事情来改变背景,这取决于 entry
的 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
}
}